조립 작업대는 프리캐드에 새롭게 내장된 작업대입니다. 오픈 소스 Ondsel 솔버를 사용합니다.
The assembly to be created consists of four parts: a Base, a Slider Rod, a Crank, and a Connecting Rod. They are connected with four joints.
Assembled parts: Base (amber), Slider Rod (light blue), Crank (red), Connecting Rod (green)
In this example all parts and the assembly are created in one document.
The cylindrical geometries of the objects are either parallel or perpendicular, the rest of the shapes is not relevant for this example unless there are clashes. With this in mind you can model your own objects or create them with the Python code below. The code will create a new document with the four objects (simpler than in the images). Just copy-paste the following lines in the Python console:
import FreeCAD as App
import FreeCADGui as Gui
import Part
doc = App.newDocument()
box1 = Part.makeBox(140, 40, 7, App.Vector(0, -20, 0))
cyl1 = Part.makeCylinder(4, 8, App.Vector(120, 0, 7))
box2 = Part.makeBox(20, 12, 10, App.Vector(5, -6, 7))
cyl2 = Part.makeCylinder(6, 20, App.Vector(25, 0, 17), App.Vector(-1, 0, 0))
cyl3 = Part.makeCylinder(4, 20, App.Vector(25, 0, 17), App.Vector(-1, 0, 0))
shape = box1.fuse([cyl1, box2, cyl2]).removeSplitter().cut(cyl3)
base = doc.addObject("Part::Feature", "Base")
base.Shape = shape
box1 = Part.makeBox(4, 12, 12, App.Vector(-12, -6, 0))
box2 = Part.makeBox(14, 12, 4, App.Vector(-8, -6, 0))
cyl1 = Part.makeCylinder(4, 8, App.Vector(0, 0, 4))
cyl2 = Part.makeCylinder(4, 88, App.Vector(-12, 0, 6),App.Vector(-1, 0, 0))
shape = box1.fuse([box2, cyl1, cyl2]).removeSplitter()
slider_rod = doc.addObject("Part::Feature", "SliderRod")
slider_rod.Shape = shape
slider_rod.Placement.Base = App.Vector(100, -40, 0)
cyl1 = Part.makeCylinder(7.5, 4)
box1 = Part.makeBox(15, 30, 4, App.Vector(-7.5, 0, 0))
cyl2 = Part.makeCylinder(7.5, 4, App.Vector(0, 30, 0))
cyl3 = Part.makeCylinder(4, 6, App.Vector(0, 30, 4))
cyl4 = Part.makeCylinder(4, 4)
shape = cyl1.fuse([box1, cyl2]).removeSplitter().fuse(cyl3).cut(cyl4)
crank = doc.addObject("Part::Feature", "Crank")
crank.Shape = shape
crank.Placement.Base = App.Vector(125, -70, 0)
cyl1 = Part.makeCylinder(6, 4)
box1 = Part.makeBox(50, 12, 4, App.Vector(0, -6, 0))
cyl2 = Part.makeCylinder(6, 4, App.Vector(50, 0, 0))
cyl3 = Part.makeCylinder(4, 4)
cyl4 = Part.makeCylinder(4, 4, App.Vector(50, 0, 0))
shape = cyl1.fuse([box1, cyl2]).removeSplitter().cut(cyl3.fuse(cyl4))
connecting_rod = doc.addObject("Part::Feature", "ConnectingRod")
connecting_rod.Shape = shape
connecting_rod.Placement.Base = App.Vector(25, -70, 0)
mat = base.ViewObject.ShapeAppearance[0]
mat.DiffuseColor = (0.80, 0.60, 0.15, 0.0)
base.ViewObject.ShapeAppearance = (mat,)
mat = slider_rod.ViewObject.ShapeAppearance[0]
mat.DiffuseColor = (0.55, 0.70, 0.70, 0.0)
slider_rod.ViewObject.ShapeAppearance = (mat,)
mat = crank.ViewObject.ShapeAppearance[0]
mat.DiffuseColor = (0.70, 0.30, 0.20, 0.0)
crank.ViewObject.ShapeAppearance = (mat,)
mat = connecting_rod.ViewObject.ShapeAppearance[0]
mat.DiffuseColor = (0.55, 0.70, 0.0, 0.0)
connecting_rod.ViewObject.ShapeAppearance = (mat,)
doc.recompute()
view = Gui.ActiveDocument.ActiveView
view.viewIsometric()
view.fitAll()
With the Create Assembly tool add an assembly to the document.
Tree view of Parts and Assembly
In the Tree view drag and drop the parts on the Assembly object. They can now be handled by the Assembly's solver.
The Parts are in the Assembly container now
To keep the assembly at the desired position, the base part should be locked, or grounded as it is called here. Select the Base in the Tree view or in the 3D view and use the Toggle grounded tool. This fixes the position of the Base in relation to the local coordinate system (LCS) of the Assembly container. A GroundedJoint object is added to the Joints container.
Expand the Joints container to find the GroundedJoint object
Instead of the two steps mentioned above it is also possible to use the Insert Link tool to place objects inside an assembly. The first object automatically becomes the grounded part. So you need to start with the Base object. The tool creates links and the original objects remain outside the assembly. To avoid confusion it is advisable to make them invisible.
A joint connects exactly two elements of different parts. They can optionally be selected before the desired joint tool is invoked (any number of selected elements other than two results in an empty selection). The elements define the position and orientation of a LCS represented by a filled circle on the local XY plane and three lines along the local X (red), Y (green), and Z (blue) axes.
Selected elements + Create Revolute Joint → rearranged Crank
Move the Crank using the left mouse button. Only a rotation around the pivot should be possible.
Selected elements + Create Slider Joint → rearranged Slider Rod
Move the SliderRod using the left mouse button. Only a displacement along its centerline should be possible.
Selected elements + Create Revolute Joint → rearranged Connecting Rod
Move the ConnectingRod using the left mouse button. Only a rotation around the pivot should be possible.
If there are several joints in a line we have to help the solver find a sensible solution.
If required, click and drag the parts into an easier to compute position.
Selected elements + Create Cylindrical Joint → finished Assembly
In the finished assembly use the mouse pointer to drag the parts according to the used joints.
The pin of the Slider Rod is redundantly orientated. Its centerline is parallel to the pin of the Base through the kinematic chain from Base via Crank and Connecting Rod, i.e. its local Z axis cannot rotate around any X or Y axis. The Slider joint also prevents the rotation of its Z axis around two local axes and so results in two redundantly constrained degrees of freedom. A Cylindrical joint instead of the Slider joint would only lock one rotation resulting in only a single redundantly constrained degree of freedom.
To control the layout of the assembly by the angle between the Base and the Crank we have to change the Revolute joint between them to a Fixed joint. To do so double-click the Revolute object in the Tree view. In the dialog change Revolute to Fixed and change the Rotation value as desired (the movement should follow the mouse wheel action).
Note that a joint type change will change the joint's Label, but not its Name. In this case the Label is changed to "Fixed".
To animate the assembly we can change the Rotation (Offset1.Angle) of the Fixed joint with Python code. Just copy-paste the following lines in the Python console:
import math
import FreeCAD as App
import FreeCADGui as Gui
actuator = App.ActiveDocument.getObjectsByLabel("Fixed")[0]
for angle in range(0, 361, 10):
# A full rotation of the Crank in steps of 10°
actuator.Offset1.Rotation.Angle = math.radians(angle)
App.ActiveDocument.recompute()
Gui.updateGui()
The end of the range must be greater than 360 to also include this angle as a valid result.
In this example a universal joint is created.
The assembly consists of three solid parts: two identical Forks and a Cross. Two additional non solid elements, Axle1 and Axle2, representing the angled axles, are also needed. The axles and the solid parts are connected with several joints.
In this example all parts and the assembly are created in one document.
The Python code below will create a new document with four objects (only 1 Fork). Just copy-paste the following lines in the Python console:
import math
import FreeCAD as App
import FreeCADGui as Gui
import Part
doc = App.newDocument()
axle1 = doc.addObject("Part::Line", "Axle1")
axle1.X2 = -80
axle1.Y2 = 0
axle1.Z2 = 0
axle2 = doc.addObject("Part::Line", "Axle2")
axle2.X2 = 80
axle2.Y2 = 0
axle2.Z2 = 0
axle2.Placement.Rotation.Angle = math.radians(20)
sph1 = Part.makeSphere(50, App.Vector(0, 0, 0), App.Vector(-1, 0, 0), 0, 90, 360)
box1 = Part.makeBox(50, 40, 80, App.Vector(-50, -20, -40))
cyl1 = Part.makeCylinder(20, 80, App.Vector(0, 0, -40))
cyl2 = Part.makeCylinder(20, 80, App.Vector(0, 0, 0), App.Vector(-1, 0, 0))
cyl3 = Part.makeCylinder(30, 60, App.Vector(0, -30, 0), App.Vector(0, 1, 0))
box2 = Part.makeBox(30, 60, 60, App.Vector(0, -30, -30))
cyl4 = Part.makeCylinder(15, 80, App.Vector(0, 0, -40))
cyl5 = Part.makeCylinder(15, 80, App.Vector(0, 0, 0), App.Vector(-1, 0, 0))
shape = sph1.common(box1).fuse([cyl1, cyl2]).cut(cyl3.fuse([box2, cyl4, cyl5]))
fork = doc.addObject("Part::Feature", "Fork")
fork.Shape = shape.removeSplitter()
fork.Placement.Base = App.Vector(0, 100, 0)
cyl1 = Part.makeCylinder(15, 80, App.Vector(0, 0, -40))
cyl2 = Part.makeCylinder(15, 80, App.Vector(0, -40, 0), App.Vector(0, 1, 0))
shape = cyl1.fuse([cyl2])
cross = doc.addObject("Part::Feature", "Cross")
cross.Shape = shape.removeSplitter()
cross.Placement.Base = App.Vector(70, 100, 0)
mat = fork.ViewObject.ShapeAppearance[0]
mat.DiffuseColor = (0.80, 0.60, 0.15, 0.0)
fork.ViewObject.ShapeAppearance = (mat,)
mat = cross.ViewObject.ShapeAppearance[0]
mat.DiffuseColor = (0.55, 0.70, 0.70, 0.0)
cross.ViewObject.ShapeAppearance = (mat,)
doc.recompute()
view = Gui.ActiveDocument.ActiveView
view.viewIsometric()
view.fitAll()
The angle between the axles is set to 20 degrees. If you want to change this value select Axle2 and change the Placement.Angle property. This property must be changed before moving Axle2 into the assembly.
Warning: parts may collide if the angle is too large.
With the Create Assembly tool add an assembly to the document.
In the Tree view drag and drop the axles on the Assembly object.
Select the two axles in the Tree view and use the Toggle grounded tool.
For the other objects we will use the Insert Component tool:
Selected elements + Create Revolute Joint + Offset of +40mm or -40mm → rearranged Fork001
If you invoke the tool first and then select the elements, you can click near the correct endpoint of Axle1 to avoid having to enter an offset.
Selected elements + Create Cylindrical Joint → rearranged Cross001
Selected elements + Create Cylindrical Joint → rearranged Fork002
Selected elements + Create Cylindrical Joint → rearranged Cross001 and Fork002
The universal joint can be driven by moving Fork001 with the left mouse.
If you want to check the situation at distinct rotation angles do the following:
In this example a vise is created.
The assembly consists of three solid parts: a fixed and a movable jaw and a screw with a lever. One additional non solid element, a crank, is also needed. The crank and the solid parts are connected with several joints.
A Screw Joint couples the translation of a part with a Slider Joint to the rotation of a part with a Revolute Joint. The screw part shall make both a translation and a rotation movement hence it must be a part with a Cylindrical Joint. In this assembly, the screw part will be coupled to the movable jaw with a Distance Joint, to the non solid crank with a Parallel Joint, and to the fixed jaw with a Cylindrical Joint.
In this example all parts and the assembly are created in one document.
The Python code below will create a new document with four objects. Just copy-paste the following lines in the Python console:
import math
import FreeCAD as App
import FreeCADGui as Gui
import Part
doc = App.newDocument()
box1 = Part.makeBox(95, 40, 75, App.Vector(0, -20, -22))
cyl1 = Part.makeCylinder(35, 80, App.Vector(0, -40, 53), App.Vector(0, 1, 0), 90)
box2 = Part.makeBox(20, 80, 30, App.Vector(-20, -40, 58))
cyl2 = Part.makeCylinder(15, 80, App.Vector(-15, -40, 58), App.Vector(0, 1, 0), 90)
box3 = Part.makeBox(5, 80, 15, App.Vector(-20, -40, 58))
box4 = Part.makeBox(35, 24, 24, App.Vector(0, -12, -12))
box5 = Part.makeBox(60, 34, 69, App.Vector(35, -17, -19))
cyl3 = Part.makeCylinder(20, 55, App.Vector(-20, -40, 53), App.Vector(1, 0, 0))
cyl4 = Part.makeCylinder(20, 55, App.Vector(-20, 40, 53), App.Vector(1, 0, 0))
cyl5 = Part.makeCylinder(5, 35, App.Vector(0, 0, 38), App.Vector(1, 0, 0))
box6 = Part.makeBox(7, 88, 15, App.Vector(-22, -44, 75))
box7 = Part.makeBox(95, 90, 10, App.Vector(0, -45, -32))
shape = box1.fuse([cyl1, box2, box6, box7]).cut(cyl2.fuse([box3, cyl3, cyl4, cyl5, box4, box5]))
fixedJaw = doc.addObject("Part::Feature", "FixedJaw")
fixedJaw.Shape = shape.removeSplitter()
fixedJaw.Placement.Rotation.Axis = App.Vector(0, 0, 1)
fixedJaw.Placement.Rotation.Angle = math.radians(180)
box1 = Part.makeBox(35, 40, 75, App.Vector(0, -20, -22))
cyl1 = Part.makeCylinder(35, 80, App.Vector(0, -40, 53), App.Vector(0, 1, 0), 90)
box2 = Part.makeBox(20, 80, 30, App.Vector(-20, -40, 58))
cyl2 = Part.makeCylinder(15, 80, App.Vector(-15, -40, 58), App.Vector(0, 1, 0), 90)
box3 = Part.makeBox(160, 24, 24, App.Vector(-160, -12, -12))
box4 = Part.makeBox(5, 80, 15, App.Vector(-20, -40, 58))
box5 = Part.makeBox(160, 18, 18, App.Vector(-160, -9, -9))
cyl3 = Part.makeCylinder(20, 55, App.Vector(-20, -40, 53), App.Vector(1, 0, 0))
cyl4 = Part.makeCylinder(20, 55, App.Vector(-20, 40, 53), App.Vector(1, 0, 0))
cyl5 = Part.makeCylinder(5, 35, App.Vector(0, 0, 38), App.Vector(1, 0, 0))
box6 = Part.makeBox(7, 88, 15, App.Vector(-22, -44, 75))
shape = box1.fuse([cyl1, box2, box3, box6]).cut(cyl2.fuse([box4, cyl3, cyl4, box5, cyl5]))
movableJaw = doc.addObject("Part::Feature", "MovableJaw")
movableJaw.Shape = shape.removeSplitter()
movableJaw.Placement.Base = App.Vector(150, 100, 0)
cyl1 = Part.makeCylinder(5, 190, App.Vector(0, 0, 0), App.Vector(1, 0, 0))
cyl2 = Part.makeCylinder(10, 20, App.Vector(190, 0, 0), App.Vector(1, 0, 0))
cyl3 = Part.makeCylinder(4, 100, App.Vector(200, 0, -50), App.Vector(0, 0, 1))
shape = cyl1.fuse([cyl2, cyl3])
leverScrew = doc.addObject("Part::Feature", "LeverScrew")
leverScrew.Shape = shape.removeSplitter()
leverScrew.Placement.Base = App.Vector(150, -100, 0)
wire1 = Part.makePolygon([App.Vector(0, 0, 100), App.Vector(0, 0, 0), App.Vector(100, 0, 0)])
crank = doc.addObject("Part::Feature", "Crank")
crank.Shape = wire1
crank.Placement.Base = App.Vector(0, -100, 0)
mat = fixedJaw.ViewObject.ShapeAppearance[0]
mat.DiffuseColor = (0.80, 0.60, 0.15, 0.0)
fixedJaw.ViewObject.ShapeAppearance = (mat,)
mat = movableJaw.ViewObject.ShapeAppearance[0]
mat.DiffuseColor = (0.55, 0.70, 0.70, 0.0)
movableJaw.ViewObject.ShapeAppearance = (mat,)
mat = leverScrew.ViewObject.ShapeAppearance[0]
mat.DiffuseColor = (0.70, 0.30, 0.20, 0.0)
leverScrew.ViewObject.ShapeAppearance = (mat,)
doc.recompute()
view = Gui.ActiveDocument.ActiveView
view.viewIsometric()
view.fitAll()
With the Create Assembly tool add an assembly to the document.
In the Tree view drag and drop the parts on the Assembly object. They can now be handled by the Assembly's solver.
To keep the assembly at the desired position, the FixedJaw part should be locked, or grounded as it is called here. Select the FixedJaw in the Tree view or in the 3D view and use the Toggle grounded tool. A GroundedJoint object is added to the Joints container.
Selected elements + Create Revolute Joint → rearranged Crank
Selected elements + Create Slider Joint → rearranged MovableJaw
Set the Min length to -77 mm and the Max length to -7 mm. This limits the opening of the vise to 70 mm.
The next three joints are necessary to force the LeverScrew to: translate like the MovableJaw, rotate like the Crank, and rotate around the main axis.
Selected elements + Create Distance Joint → rearranged LeverScrew
Select two faces. Set the distance value to 20 mm.
Selected elements + Create Parallel Joint → rearranged LeverScrew
Selected elements + Create Cylindrical Joint → rearranged LeverScrew
Selected elements (LeverScrew invisible) + Create Screw Joint → complete vise mechanism (LeverScrew visible)
If necessary make the LeverScrew invisible during selection.
Set the Pitch radius to 5 mm
The vise can be driven by moving Crank or MovableJaw with the left mouse.
In this example a shock absorber is created.
The assembly consists of three solid parts: a piston, a cylinder and a spring. Three additional non solid elements, two axles and a rod are also needed. All parts are connected with several joints.
The hinge of the piston rotates around Axle2, while the hinge of the cylinder moves on an arc of circle centered on Axle1. The non solid Rod is used for this movement. The length of the Rod is the radius of the arc.
The Python code below will create a new document with 6 objects. Create a new macro and copy-paste the code below in the Python editor (not in the Python console). Then run the macro.
The code below cannot be run from the Python console because the spring must be a Part::FeaturePython object defined by of a class with the callback functions execute()
and onChanged()
. Only then can its height be changed via a property.
import math
import FreeCAD as App
import FreeCADGui as Gui
import Part
doc = App.newDocument()
class Spring():
def __init__(self, spring):
spring.addProperty("App::PropertyLength", "Height", "Spring", "Height of the helix").Height = 200.0
spring.Proxy = self
spring.ViewObject.Proxy = 0
def execute(self, spring):
helix = Part.makeHelix(spring.Height/8.5, spring.Height, 35)
startPnt = helix.Edges[0].Curve.value(0)
section = Part.Wire([Part.Circle(startPnt, App.Vector(0, 1, 0), 5).toShape()])
hel1 = helix.makePipeShell([section], True, True)
box1 = Part.makeBox(80, 80, 10, App.Vector(-40, -40, -10))
box2 = Part.makeBox(80, 80, 10, App.Vector(-40, -40, spring.Height))
shape = hel1.cut(box1).cut(box2)
spring.Shape = shape
def onChanged(self, spring, prop):
if prop == "Height":
self.execute(spring)
spring = doc.addObject("Part::FeaturePython", "Spring")
Spring(spring)
spring.Placement.Base = App.Vector(0, 100, 0)
axle1 = doc.addObject("Part::Line", "Axle1")
axle1.X2 = 0
axle1.Y2 = 80
axle1.Z2 = 0
axle2 = doc.addObject("Part::Line", "Axle2")
axle2.X2 = 0
axle2.Y2 = 80
axle2.Z2 = 0
axle2.Placement.Base = App.Vector(120, 0, -250)
rod = doc.addObject("Part::Line", "Rod")
rod.X2 = 100
rod.Y2 = 0
rod.Z2 = 0
rod.Placement.Base = App.Vector(0, -50, 0)
cyl1 = Part.makeCylinder(40, 10,App.Vector(0, 0, -5))
tor1 = Part.makeTorus(40, 5)
cyl2 = Part.makeCylinder(45, 5)
box1 = Part.makeBox(30, 10, 30,App.Vector(-15, -5, -35))
cyl3 = Part.makeCylinder(15, 10, App.Vector(0, -5, -35), App.Vector(0, 1, 0))
cyl4 = Part.makeCylinder(40, 5)
cyl5 = Part.makeCylinder(5, 10,App.Vector(0, -5, -35), App.Vector(0, 1, 0))
cyl6 = Part.makeCylinder(5, 130)
cyl7 = Part.makeCylinder(20, 5,App.Vector(0, 0, 130))
shape = cyl1.fuse([tor1,cyl2, box1, cyl3]).cut(cyl4.fuse([cyl5])).fuse([cyl6, cyl7])
piston = doc.addObject("Part::Feature", "Piston")
piston.Shape = shape.removeSplitter()
piston.Placement.Base = App.Vector(200, 100, -200)
cyl1 = Part.makeCylinder(40, 10,App.Vector(0, 0, -5))
tor1 = Part.makeTorus(40, 5)
cyl2 = Part.makeCylinder(45, 5)
box1 = Part.makeBox(30, 10, 30,App.Vector(-15, -5, -35))
cyl3 = Part.makeCylinder(15, 10,App.Vector(0, -5, -35), App.Vector(0, 1, 0))
cyl4 = Part.makeCylinder(40, 5)
cyl5 = Part.makeCylinder(5, 10,App.Vector(0, -5, -35), App.Vector(0, 1, 0))
cyl6 = Part.makeCylinder(25, 130)
tor2 = Part.makeTorus(20, 5,App.Vector(0, 0, 130))
cyl7 = Part.makeCylinder(20, 135)
cyl8 = Part.makeCylinder(20, 130)
cyl9 = Part.makeCylinder(5, 135)
shape = cyl1.fuse([tor1, cyl2, box1, cyl3]).cut(cyl4.fuse([cyl5])).fuse([cyl6, tor2, cyl7]).cut(cyl8.fuse([cyl9]))
cylinder = doc.addObject("Part::Feature", "Cylinder")
cylinder.Shape = shape.removeSplitter()
cylinder.Placement.Rotation.Axis = App.Vector(0, 1, 0)
cylinder.Placement.Rotation.Angle = math.pi
cylinder.Placement.Base = App.Vector(100, 100, 0)
mat = piston.ViewObject.ShapeAppearance[0]
mat.DiffuseColor = (0.80, 0.60, 0.15, 0.0)
piston.ViewObject.ShapeAppearance = (mat,)
mat = cylinder.ViewObject.ShapeAppearance[0]
mat.DiffuseColor = (0.55, 0.70, 0.70, 0.0)
cylinder.ViewObject.ShapeAppearance = (mat,)
doc.recompute()
view = Gui.ActiveDocument.ActiveView
view.viewIsometric()
view.fitAll()
With the Create Assembly tool add an assembly to the document.
In the Tree view drag and drop the parts on the Assembly object. They can now be handled by the Assembly's solver.
To keep the assembly at the desired position, the two axles should be locked, or grounded as it is called here. Select the two axles in the Tree view or in the 3D view and use the Toggle grounded tool. Two GroundedJoint objects are added to the Joints container.
Create Revolute Joint + Selected elements → rearranged Piston
Create Slider Joint + Selected elements → rearranged and moved Cylinder
Please pay attention to the location of the coordinate system before selecting a face. It should be in the center of each face.
Drag the Cylinder to create some distinct between it and the Piston. The supporting faces for the Spring should be visible.
Create Distance Joint + Selected faces → rearranged Cylinder Distance set to 200 mm
Set the distance value to 200 mm.
The next two joints are necessary to force the hinge of the Cylinder to move on an arc of circle.
Create Cylindrical Joint + Selected elements → rearranged Rod
Make sure the Z axis of the coordinate system (blue) is perpendicular to the Rod by selecting an endpoint.
Create Revolute Joint + Selected elements → rearranged Cylinder
Again make sure the Z axis of the coordinate system (blue) is perpendicular to the Rod.
You may encounter problems with this joint. If that is the case try the following:
The next two joints are necessary to fix the Spring to the support face.
Create Parallel Joint + Selected faces → rearranged Spring
Select the center of the support face on the Piston and the center of the bottom face of the spring. Keep the distance value 0.
Create Fixed Joint + Selected elements → rearranged Spring
Select the bottom vertex of the cylinder's seam in the Piston and the corner vertex in the Spring.
<<Distance>>.Distance
To do so double-click the Distance object in the Tree view and change its Distance property. Recompute the document. The spring changes its length.